package jsr292.weaver;

import static org.objectweb.asm.Opcodes.CHECKCAST;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import jsr292.weaver.opt.Optimizer;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.commons.RemappingClassAdapter;
import org.objectweb.asm.tree.MethodNode;

public class OnlineWeaver extends RemappingClassAdapter {
    final boolean retransform; // if VM support retransform
    MethodNode staticInit; // reified static init
    
    // possible constants are asm.MethodHandle, asm.MethodType, boxed Primitive, asm.Type or List
    HashMap<Object,ConstantInfo> constantMap =
            new HashMap<Object,ConstantInfo>(); 
    ArrayList<Object> constants =
            new ArrayList<Object>();
    int callSiteCounter;

    String ownerClassName;
    int stubCount;

    private static final boolean ENABLE_OPTIMIZER;
    static {
	ENABLE_OPTIMIZER = Boolean.parseBoolean(System.getProperty("jsr292.backport.optimizer", "true"));
    }

    public OnlineWeaver(ClassVisitor cv, boolean retransform) {
	super(cv, new Remapper() {
	    @Override
	    public String map(String typeName) {
		if (typeName.startsWith(Utils.JAVA_LANG_INVOKE))
		    return Utils.JSR292_PACKAGE_PREFIX + typeName;
		if (typeName.equals(Utils.JAVA_LANG_CLASS_VALUE))
		    return Utils.JSR292_CLASS_VALUE;
		if (typeName.equals(Utils.JAVA_LANG_UTIL_OBJECTS))
                    return Utils.JSR292_OBJECTS;
		return typeName;
	    }
	});
	this.retransform = ENABLE_OPTIMIZER & retransform;
    }

    MethodVisitor superVisitMethod(int access, String name, String desc) {
	return super.visitMethod(access, name, desc, null, null);
    }

    void createMHSiteFields(int stubId) {
	FieldVisitor mhField = super.visitField(Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC,
		"$method_handle_stub" + stubId + "$mh", Utils.JSR292_METHOD_HANDLE_DESC, null, null);
	mhField.visitEnd();

	FieldVisitor counterField = super.visitField(Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC,
		"$method_handle_stub" + stubId + "$counter", "I", null, null);
	counterField.visitEnd();
    }

    void createIndySiteFields(String stubName) {
	FieldVisitor mhField = super.visitField(Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC,
		stubName + "cs", Utils.JSR292_CALLSITE_DESC, null, null);
	mhField.visitEnd();
    }
    
    void createMHOrMTConstantField(int constantId, String desc) {
        FieldVisitor mhField = super.visitField(Opcodes.ACC_PRIVATE|Opcodes.ACC_STATIC|Opcodes.ACC_FINAL,
                "$mh_cst" + constantId + '$', desc, null, null);
        mhField.visitEnd();
    }
    
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
	ownerClassName = name;
	super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) {
	// store static init
	if ("<clinit>".equals(name) && "()V".equals(desc)) {
	    staticInit = new MethodNode(access, name, desc, signature, exceptions);
	    
	    // and instrument it
	    return new OnFlyAdapter(staticInit);
	}

	MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
	return new OnFlyAdapter(mv);
    }

    @Override
    public void visitEnd() {
	MethodNode staticInit = this.staticInit;
	if (staticInit == null) {
	    if (constants.isEmpty()) {
		super.visitEnd();
	        return;
	    }
	    
	    MethodVisitor mv = superVisitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V");
	    mv.visitCode();
	    generateSyntheticStaticInit(mv);
	    mv.visitInsn(Opcodes.RETURN);
	    mv.visitMaxs(0, 0);
	    mv.visitEnd();

	    super.visitEnd();
	    return;
	}

	@SuppressWarnings("unchecked")  // ASM API is not generified
	String[] exceptions = (String[])staticInit.exceptions.toArray(
		new String[staticInit.exceptions.size()]);
	
	MethodVisitor mv = super.visitMethod(staticInit.access,
		staticInit.name,
		staticInit.desc,
		staticInit.signature,
		exceptions);
	
	staticInit.accept(new MethodVisitor(Opcodes.ASM4, mv) {
            @Override
            public void visitCode() {
                super.visitCode();
                if (!constants.isEmpty()) {
                    generateSyntheticStaticInit(mv); 
                }
            }
        });
	
        super.visitEnd();
    }
    
    
    private static void genSyntheticMethodType(MethodVisitor mv, Type methodType) {
        mv.visitLdcInsn(methodType.getDescriptor());
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, Utils.JSR292_METHODTYPE, "fromMethodDescriptorString",
                "(Ljava/lang/String;Ljava/lang/ClassLoader;)" + Utils.JSR292_METHODTYPE_DESC);
    }
    
    private void genSyntheticMethodHandle(MethodVisitor mv, Handle cst) {
        String name;
        String desc = '('+Utils.JSR292_LOOKUP_DESC+"Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)"+Utils.JSR292_METHOD_HANDLE_DESC;
        int tag = cst.getTag();
        switch(tag) {
        case Opcodes.H_GETFIELD:
            name = "getField";
            break;
        case Opcodes.H_GETSTATIC:
            name = "getStatic";
            break;
        case Opcodes.H_PUTFIELD:
            name = "putField";
            break;
        case Opcodes.H_PUTSTATIC:
            name = "putStatic";
            break;
        case Opcodes.H_INVOKESTATIC:
            name = "invokeStatic";
            break;

        case Opcodes.H_INVOKESPECIAL:
            name = "invokeSpecial";
            desc = '('+Utils.JSR292_LOOKUP_DESC+"Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)"+Utils.JSR292_METHOD_HANDLE_DESC;
            break;
        case Opcodes.H_NEWINVOKESPECIAL:
            name = "invokeConstructor";
            desc = '('+Utils.JSR292_LOOKUP_DESC+"Ljava/lang/Class;Ljava/lang/String;)"+Utils.JSR292_METHOD_HANDLE_DESC;
            break;
        default:
        //case Opcodes.MH_INVOKEVIRTUAL:
        //case Opcodes.MH_INVOKEINTERFACE:
            name = "invokeVirtual";
            break;
        }

        mv.visitVarInsn(Opcodes.ALOAD, 0);   // load lookup
        mv.visitLdcInsn(Type.getObjectType(cst.getOwner()));
        if (tag != Opcodes.H_NEWINVOKESPECIAL) {
            mv.visitLdcInsn(cst.getName());
        }
        
        mv.visitLdcInsn(cst.getDesc());
       
        if (tag == Opcodes.H_INVOKESPECIAL) {
            mv.visitLdcInsn(Type.getObjectType(ownerClassName));
        }
        
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, Utils.JSR292_CONSTANT_METHOD_HANDLES, name, desc);
    }
    
    private void genSyntheticArray(MethodVisitor mv, BitSet registerSet, Object[] csts) {
        mv.visitLdcInsn(3 + csts.length); // lookup + name + methodType + array.length
        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
        for(int i=0; i<csts.length; i++) {
            mv.visitInsn(Opcodes.DUP);
            Utils.ldcInteger(mv, 3 + i);
            Object cst = csts[i];
            genBootstrapArgument(mv, registerSet, cst);
            boxIfNecessary(mv, cst);
            mv.visitInsn(Opcodes.AASTORE);
        }
    }
    
    private static void boxIfNecessary(MethodVisitor mv, Object o) {
        if (o instanceof Integer) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
            return;
        }
        if (o instanceof Long) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(L)Ljava/lang/Long;");
            return;
        }
        if (o instanceof Double) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
            return;
        }
        if (o instanceof Float) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
            return;
        }
    }
    
    private ConstantInfo genBootstrapArgument(MethodVisitor mv, BitSet registerSet, Object cst) {
        // constant values understood by java 5 VM
        if (cst.getClass().getName().startsWith("java.lang.")) {
            if (cst instanceof Integer) {
                Utils.ldcInteger(mv, (Integer)cst);
                return null;
            }
            // TODO, may be generate DCONST, FCONST and LCONST
            mv.visitLdcInsn(cst);
            return null;
        }
        if (cst instanceof Type && ((Type)cst).getSort() != Type.METHOD) {
            mv.visitLdcInsn(cst);
            return null;
        }
        return genSyntheticConstant(mv, registerSet, cst);
    }
    
    private ConstantInfo genSyntheticConstant(MethodVisitor mv, BitSet registerSet, Object cst) {
        ConstantInfo constantInfo = constantMap.get(cst);
        int slot = constantInfo.localVarSlot; 
        if (slot == ConstantInfo.NO_SLOT) {
            if (cst instanceof Type) {
                Type type = (Type)cst;
                if (type.getSort() == Type.METHOD) {
                  genSyntheticMethodType(mv, type);
                }
            } else {
                if (cst instanceof Handle) {
                    genSyntheticMethodHandle(mv, (Handle)cst);
                } else {
                    genSyntheticArray(mv, registerSet, ((List<?>)cst).toArray());
                }
            }
            
            if (constantInfo.sharedCounter != 0) {
                slot = constantInfo.localVarSlot = registerSet.nextClearBit(0);
                registerSet.set(slot);
                constantInfo.sharedCounter--;
                mv.visitInsn(Opcodes.DUP);
                mv.visitVarInsn(Opcodes.ASTORE, 2 + slot);
            }
        } else {
            mv.visitVarInsn(Opcodes.ALOAD, 2 + slot);
            if (constantInfo.sharedCounter != 0) {
                constantInfo.sharedCounter--;
            } else {
                registerSet.clear(constantInfo.localVarSlot);
            }
        }
        
        return constantInfo;
    }
    
    private void genSynthethicCallSite(MethodVisitor mv, BitSet registerSet, CallSiteInfo callSiteInfo) {
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        
        mv.visitLdcInsn(callSiteInfo.methodName);
        mv.visitLdcInsn(callSiteInfo.desc);
        
        genSyntheticConstant(mv, registerSet, callSiteInfo.bsm);
        
        String bootstrapCallSiteDesc;
        Object[] bsmArgs = callSiteInfo.bsmArgs;
        switch(bsmArgs.length) {
        case 0:
            bootstrapCallSiteDesc = '('+Utils.JSR292_LOOKUP_DESC+"Ljava/lang/String;Ljava/lang/String;"+Utils.JSR292_METHOD_HANDLE_DESC+"Ljava/lang/String;)"+Utils.JSR292_BOOTSTRAP_CALLSITE_DESC;
            break;
        case 1: {
            Object bsmArg = bsmArgs[0];
            genBootstrapArgument(mv, registerSet, bsmArg);
            boxIfNecessary(mv, bsmArg);
            bootstrapCallSiteDesc = '('+Utils.JSR292_LOOKUP_DESC+"Ljava/lang/String;Ljava/lang/String;"+Utils.JSR292_METHOD_HANDLE_DESC+"Ljava/lang/Object;Ljava/lang/String;)"+Utils.JSR292_BOOTSTRAP_CALLSITE_DESC;
            break;
        }
        default:
            genSyntheticConstant(mv, registerSet, Arrays.asList(bsmArgs));
            bootstrapCallSiteDesc = '('+Utils.JSR292_LOOKUP_DESC+"Ljava/lang/String;Ljava/lang/String;"+Utils.JSR292_METHOD_HANDLE_DESC+"[Ljava/lang/Object;Ljava/lang/String;)"+Utils.JSR292_BOOTSTRAP_CALLSITE_DESC;
        }
        
        mv.visitLdcInsn(callSiteInfo.stubName + "cs");
        
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, Utils.JSR292_BOOTSTRAP_CALLSITE,
                "create", bootstrapCallSiteDesc);
    }
    
    void generateSyntheticStaticInit(MethodVisitor mv) {
        mv.visitLdcInsn(Type.getObjectType(ownerClassName));
        mv.visitInsn(Opcodes.DUP);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, Utils.JSR292_METHOD_HANDLES, "lookup", "(Ljava/lang/Class;)"+Utils.JSR292_LOOKUP_DESC);
        mv.visitVarInsn(Opcodes.ASTORE, 0);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;");
        mv.visitVarInsn(Opcodes.ASTORE, 1);
        
        // 1   [ current classloader ]
        // 0   [ current lookup      ]
        // stacks:
        
        BitSet registerSet = new BitSet();
        for(Object cst: constants) {
            // callsite
            if (cst instanceof CallSiteInfo) {
                CallSiteInfo callSiteInfo = (CallSiteInfo)cst;
                genSynthethicCallSite(mv, registerSet, callSiteInfo);
                mv.visitFieldInsn(Opcodes.PUTSTATIC, ownerClassName, callSiteInfo.stubName + "cs", Utils.JSR292_CALLSITE_DESC);
                continue;
            }
            
            // constant method type or constant method handle
            ConstantInfo info = genSyntheticConstant(mv, registerSet, cst);
            mv.visitFieldInsn(Opcodes.PUTSTATIC, ownerClassName, "$mh_cst" + info.index + '$',
                    (cst instanceof Handle)? Utils.JSR292_METHOD_HANDLE_DESC: Utils.JSR292_METHODTYPE_DESC);
        }
    }

    static class ConstantInfo {
        final int index;
        int sharedCounter;
        int localVarSlot = NO_SLOT;
        
        static final int NO_SLOT = -1;
        
        public ConstantInfo(int index) {
            this.index = index;
        }
    }
    
    static class CallSiteInfo {
        final String stubName;
	final String methodName;
	final String desc;
	final Handle bsm;
	final Object[] bsmArgs;
	
	public CallSiteInfo(String stubName, String methodName, String desc, Handle bsm, Object[] bsmArgs) {
	    this.stubName = stubName;
	    this.methodName = methodName;
	    this.desc = desc;
	    this.bsm = bsm;
	    this.bsmArgs = bsmArgs;
	}
    }
    
    ConstantInfo getInternedConstant(Object cst) {
        ConstantInfo info = constantMap.get(cst);
        if (info == null) {
            int index = constantMap.size();
            info = new ConstantInfo(index);
            constantMap.put(cst, info);
            return info;
        }
        
        info.sharedCounter ++;
        return info;
    }
    
    ConstantInfo getInternedConstants(Object[] csts) {
        ConstantInfo info = getInternedConstant(Arrays.asList(csts));
        if (info.sharedCounter != 0) {
            return info;
        }
        for(Object cst: csts) {
            if (cst instanceof Handle || (cst instanceof Type && ((Type)cst).getSort() == Type.METHOD)) {
                getInternedConstant(cst);
            }
        }
        return info;
    }
    
    class OnFlyAdapter extends MethodVisitor {
	OnFlyAdapter(MethodVisitor mv) {
	    super(Opcodes.ASM4, mv);
	}

	@Override
	public void visitLdcInsn(Object cst) {
	    boolean isAMethodHandle = cst instanceof Handle;
	    if (isAMethodHandle || (cst instanceof Type && ((Type)cst).getSort() == Type.METHOD)) {
	        ConstantInfo info = getInternedConstant(cst);
	        String desc = (isAMethodHandle)?  Utils.JSR292_METHOD_HANDLE_DESC: Utils.JSR292_METHODTYPE_DESC;
	        if (info.sharedCounter == 0) {
	          constants.add(cst);
	          createMHOrMTConstantField(info.index, desc);
	        }
	        super.visitFieldInsn(Opcodes.GETSTATIC, ownerClassName, "$mh_cst" + info.index + '$', desc);
	        return;
	    }
	    super.visitLdcInsn(cst);
	}
	
	
	
	@Override
	public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
	    switch(bsmArgs.length) {
	    case 0:
	        break;
	    case 1:
	        Object cst = bsmArgs[0];
	        if (cst instanceof Handle || (cst instanceof Type &&  ((Type)cst).getSort() == Type.METHOD)) {
	            getInternedConstant(cst);
	        }
	        break;
	    default:
	        getInternedConstants(bsmArgs);        
	    }
            
            getInternedConstant(bsm);
            
            int stubId = callSiteCounter++;
            String stubName = "$indy_stub" + stubId + name + '$';
            
            CallSiteInfo callSiteInfo = new CallSiteInfo(stubName, name, desc, bsm, bsmArgs);
            constants.add(callSiteInfo);
            
            MethodVisitor mv = superVisitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, stubName, desc);
            generateIndyStub(mv, stubName, desc);

            createIndySiteFields(stubName);
            
            super.visitMethodInsn(Opcodes.INVOKESTATIC, ownerClassName, stubName, desc);
	}
	
	@Override
	public void visitMethodInsn(int opcode, String owner, String name, String desc) {
	    if (opcode == Opcodes.INVOKEVIRTUAL &&
		owner.equals(Utils.JAVA_METHOD_HANDLE) &&
		("invoke".equals(name) ||
		 "invokeExact".equals(name) ||
		 "invokeGeneric".equals(name))) {  // also support old syntax

		String stubDesc = '(' + Utils.JSR292_METHOD_HANDLE_DESC + desc.substring(1);

		int stubId = stubCount++;
		String stubName = "$method_handle_stub" + stubId + '$';
		MethodVisitor mv = superVisitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, stubName, stubDesc);
		generateMHStub(mv, stubId, desc);

		if (retransform) {
		    createMHSiteFields(stubId);
		}

		super.visitMethodInsn(Opcodes.INVOKESTATIC, ownerClassName, stubName, stubDesc);
		return;
	    }

	    if (opcode == Opcodes.INVOKESTATIC) {
		if (owner.startsWith(Utils.JAVA_LANG_INVOKE + '/')) {
		    String sig = owner + '.' + name + desc;
		    if (CALLER_CLASS_SET.contains(sig)) {
			int index = desc.indexOf(')');
			desc = desc.substring(0, index) + "Ljava/lang/Class;" + desc.substring(index);
			
			mv.visitLdcInsn(Type.getObjectType(ownerClassName));
		    }
		}
	    }   

	    super.visitMethodInsn(opcode, owner, name, desc);
	}
    }
    
    static final HashSet<String> CALLER_CLASS_SET;
    static {
	HashSet<String> set = new HashSet<String>();
	set.add(Utils.JAVA_METHOD_HANDLES+".lookup()"+Utils.JAVA_LOOKUP_DESC);
	CALLER_CLASS_SET = set;
    }

    void generateMHStub(MethodVisitor mv, int stubId, String desc) {
	mv.visitCode();

	if (retransform) {
	    generateMethodHandleInvokePreambule(mv, stubId);
	}
	// XXX else insert a method type check here !!!

	// load method handle
	mv.visitVarInsn(Opcodes.ALOAD, 0);

	generateGenericCall(mv, desc, 1);

	Type returnType = Type.getReturnType(desc);
	mv.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
	
	mv.visitMaxs(0, 0);
	mv.visitEnd();
    }

    void generateIndyStub(MethodVisitor mv, String stubName, String desc) {
	mv.visitCode();

	if (retransform) {
	    generateIndyInvokePreambule(mv, stubName);
	    
	} else {
	    // load method handle
	    mv.visitFieldInsn(Opcodes.GETSTATIC, ownerClassName,
		    stubName+ "cs", Utils.JSR292_CALLSITE_DESC);
	    mv.visitFieldInsn(Opcodes.GETFIELD, Utils.JSR292_CALLSITE,
		    "target", Utils.JSR292_METHOD_HANDLE_DESC);
	}
	generateGenericCall(mv, desc, 0);

	Type returnType = Type.getReturnType(desc);
	mv.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
	
	mv.visitMaxs(0, 0);
	mv.visitEnd();
    }

    private void generateMethodHandleInvokePreambule(MethodVisitor mv, int stubId) {
	mv.visitFieldInsn(Opcodes.GETSTATIC, ownerClassName,
		"$method_handle_stub" + stubId + "$mh",
		Utils.JSR292_METHOD_HANDLE_DESC);
	mv.visitVarInsn(Opcodes.ALOAD, 0);

	Label cacheMissLabel = new Label();
	mv.visitJumpInsn(Opcodes.IF_ACMPNE, cacheMissLabel);

	// check counter
	mv.visitFieldInsn(Opcodes.GETSTATIC, ownerClassName,
		"$method_handle_stub" + stubId + "$counter", "I");
	mv.visitInsn(Opcodes.DUP);

	Utils.ldcInteger(mv, Optimizer.OPTIMIZER_MH_THRESHOLD);
	Label retransformLabel = new Label();
	mv.visitJumpInsn(Opcodes.IF_ICMPGE, retransformLabel);

	// fast path: update counter
	// stack: [ counter ]
	mv.visitInsn(Opcodes.ICONST_1);
	mv.visitInsn(Opcodes.IADD);
	mv.visitFieldInsn(Opcodes.PUTSTATIC, ownerClassName,
		"$method_handle_stub" + stubId + "$counter", "I");

	Label endLabel = new Label();
	mv.visitJumpInsn(Opcodes.GOTO, endLabel);

	// cache miss: => put new method handle in cache, reset counter
	// stack: <empty>
	mv.visitLabel(cacheMissLabel);

	// XXX: insert a method handle type check here

	mv.visitVarInsn(Opcodes.ALOAD, 0);
	mv.visitFieldInsn(Opcodes.PUTSTATIC, ownerClassName,
		"$method_handle_stub" + stubId + "$mh",
		Utils.JSR292_METHOD_HANDLE_DESC);

	mv.visitInsn(Opcodes.ICONST_0);
	mv.visitFieldInsn(Opcodes.PUTSTATIC, ownerClassName,
		"$method_handle_stub" + stubId + "$counter", "I");

	mv.visitJumpInsn(Opcodes.GOTO, endLabel);

	// retransform:
	// stack: [ counter ]
	mv.visitLabel(retransformLabel);
	mv.visitInsn(Opcodes.POP); // pop counter
	
	//mv.visitLdcInsn(Integer.MIN_VALUE);
	//mv.visitFieldInsn(Opcodes.PUTSTATIC, ownerClassName,
        //        "$method_handle_stub" + stubId + "$counter", "I");

	mv.visitLdcInsn(Type.getObjectType(ownerClassName));
	mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/opt/Optimizer",
		"retransform", "(Ljava/lang/Class;)V");

	// end:
	mv.visitLabel(endLabel);
    }
    
    private void generateIndyInvokePreambule(MethodVisitor mv, String stubName) {
	mv.visitFieldInsn(Opcodes.GETSTATIC, ownerClassName,
		stubName+ "cs", Utils.JSR292_CALLSITE_DESC);
	mv.visitInsn(Opcodes.DUP);
	mv.visitInsn(Opcodes.DUP);
	mv.visitFieldInsn(Opcodes.GETFIELD, Utils.JSR292_CALLSITE,
		"counter", "I");
	mv.visitInsn(Opcodes.DUP);
	
	Utils.ldcInteger(mv, Optimizer.OPTIMIZER_INDY_THRESHOLD);
	Label retransformLabel = new Label();
	mv.visitJumpInsn(Opcodes.IF_ICMPGE, retransformLabel);

	// fast path: update counter
	//        [ callsite ]
	//        [ callsite ]
	// stack: [ counter  ]
	mv.visitInsn(Opcodes.ICONST_1);
	mv.visitInsn(Opcodes.IADD);
	mv.visitFieldInsn(Opcodes.PUTFIELD, Utils.JSR292_CALLSITE,
		"counter", "I");

	Label endLabel = new Label();
	mv.visitJumpInsn(Opcodes.GOTO, endLabel);
	
	// retransform:
	//	      [ callsite ]
	//        [ callsite ]
	// stack: [ counter  ]
	mv.visitLabel(retransformLabel);
	mv.visitInsn(Opcodes.POP); // pop counter
	mv.visitInsn(Opcodes.POP); // pop callsite

	//mv.visitLdcInsn(Integer.MIN_VALUE);
	//mv.visitFieldInsn(Opcodes.PUTFIELD, Utils.JSR292_CALLSITE,
        //        "counter", "I");
	
	// ask for optimization
	mv.visitLdcInsn(Type.getObjectType(ownerClassName));
	mv.visitMethodInsn(Opcodes.INVOKESTATIC, "jsr292/weaver/opt/Optimizer",
	    "retransform", "(Ljava/lang/Class;)V");

	// end:
	// stack: [ callsite ]
	mv.visitLabel(endLabel);
	mv.visitFieldInsn(Opcodes.GETFIELD, Utils.JSR292_CALLSITE,
	    "target", Utils.JSR292_METHOD_HANDLE_DESC);
    }

    /**
     * This code is similar to OptimizerSnippet.genericCall and must be updated accordingly
     */
    public static void generateGenericCall(MethodVisitor mv, String desc, int startSlot) {
	Type[] types = Type.getArgumentTypes(desc);

	int argumentCount = types.length;
	boolean arrayStore = argumentCount > Utils.PARAMETER_THRESHOLD;
	if (arrayStore) {
	    Utils.ldcInteger(mv, argumentCount); // array length
	    mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
	}

	int slot = startSlot;
	for (int index = 0; index < argumentCount; index++) {
	    Type type = types[index];
	    if (arrayStore) {
		mv.visitInsn(Opcodes.DUP);
		Utils.ldcInteger(mv, index);
	    }

	    mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), slot);

	    slot += type.getSize();
	    Wrapper wrapper = Wrapper.WRAPPERS[type.getSort()];
	    if (wrapper != null) {
		mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapper.wrapperClassName, "valueOf", wrapper.boxingDesc);
	    }

	    if (arrayStore) {
		mv.visitInsn(Opcodes.AASTORE);
	    }
	}

	mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Utils.JSR292_METHOD_HANDLE, "$invoke$", invokeMethodDescriptor(argumentCount));

	Type returnType = Type.getReturnType(desc);
	generateGenericCallEpilog(mv, returnType);
    }
    
    public static void generateGenericCallEpilog(MethodVisitor mv, Type returnType) {
	int sort = returnType.getSort();
	String internalClassName;
	if (sort == Type.OBJECT || sort == Type.ARRAY) {
	    internalClassName = returnType.getInternalName();
	    if (!"java/lang/Object".equals(internalClassName)) {
		mv.visitTypeInsn(CHECKCAST, internalClassName);
	    }
	    return;
	}
	if (sort == Type.VOID) {
	    mv.visitInsn(Opcodes.POP);
	    return;
	}

	Wrapper wrapper = Wrapper.WRAPPERS[sort];
	internalClassName = wrapper.wrapperClassName;
	mv.visitTypeInsn(CHECKCAST, internalClassName);
	mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, internalClassName,
		wrapper.unboxingMethodName, wrapper.unboxingDesc);
    }

    public static String invokeMethodDescriptor(int argumentCount) {
	if (argumentCount > Utils.PARAMETER_THRESHOLD)
	    return "([Ljava/lang/Object;)Ljava/lang/Object;";

	StringBuilder builder = new StringBuilder(18 * argumentCount + 20)
		.append("(");
	for (int i = 0; i < argumentCount; i++) {
	    builder.append("Ljava/lang/Object;");
	}
	return builder.append(")Ljava/lang/Object;").toString();
    }
}
